package com.phychips.bluetooth;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;

import com.phychips.common.OnBytesAvailableListener;
import com.phychips.rcp.iBluetoothEvent;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.AsyncTask;

public class BluetoothTransceiver
{
    private static final boolean D = true;
    private static final String ClassName = "BluetoothApi";
    private static iBluetoothEvent mBluetoothEvent = null;

    private static BluetoothAdapter mAdapter = null;
    private String m_btDestAddr = null;
    private ConnectThread mConnectThread;
    private ConnectedThread mConnectedThread;
    private int mState;

    private static boolean mAutoread = false;

    // Constants that indicate the current connection state
    public static final int STATE_NONE = 0; // we're doing nothing
    public static final int STATE_LISTEN = 1; // now listening for incoming
					      // connections
    public static final int STATE_CONNECTING = 2; // now initiating an outgoing
						  // connection
    public static final int STATE_CONNECTED = 3; // now connected to a remote
						 // device

    // Well known SPP UUID (will *probably* map to RFCOMM channel 1 (default) if
    // not in use);
    // see comments in onResume().
    private static final UUID SPP_UUID = UUID
	    .fromString("00001101-0000-1000-8000-00805F9B34FB");

    final private static byte SCP_HEADER = (byte) 0xaa;
    final private static byte SCP_TAIL = (byte) 0x55;
    final private static byte SCP_MIN_LEN = 4;

    final private static byte RCP_PKT_PRAMBL = (byte) 0xBB;
    private byte RCP_PKT_ENDMRK = (byte) 0x7E;
    private byte RCP_PRAMBL_LEN = 1;
    private byte RCP_HEADER_LEN = 4;
    private byte RCP_ENDMRK_LEN = 1;
    private byte RCP_HEADEND_LEN = (byte) (RCP_PRAMBL_LEN + RCP_HEADER_LEN + RCP_ENDMRK_LEN);
    // private byte RCP_CRC_LEN = 2;
    private byte RCP_CRC_LEN = 0;
    private byte RCP_HEADEND_CRC_LEN = (byte) (RCP_HEADEND_LEN + RCP_CRC_LEN);

    private List<Byte> m_incomingRcp = new ArrayList<Byte>();
    private List<Byte> m_incomingScp = new ArrayList<Byte>();
    public static final int MAX_BUF_SIZE = 1024;
    private int m_SleepTime = 200;

    private int comMode = 0;
    private static final int MODE_NONE = 0;
    private static final int MODE_RCP = 1;
    private static final int MODE_SCP = 2;

    final public static int HANDLER_MODE_SR9_CONNECT = (byte) 0x01;
    final public static int HANDLER_MODE_SR9_DISCONNECT = (byte) 0x02;
    final public static int HANDLER_MODE_RFID_TIMEOUT = (byte) 0x03;
    final public static int HANDLER_MODE_SR9_BATTERY_LOW = (byte) 0x04;
    final public static int HANDLER_MODE_RCP_RECEIVE = (byte) 0x05;
    final public static int HANDLER_MODE_RCP_RECEIVE_REST = (byte) 0x06;

    final public static byte CMD_CONNECT = (byte) 0xe9;
    final public static byte CMD_DISCONNECT = (byte) 0xea;
    final public static byte CMD_TIMEOUT = (byte) 0xeb;
    final public static byte CMD_BATTERY_LOW = (byte) 0xec;
    final public static byte CMD_UART_ERR = (byte) 0xed;

    public BluetoothTransceiver(BluetoothAdapter adapter)
    {
	mAdapter = adapter;
    }

    public void setBluetoothEvent(iBluetoothEvent e)
    {
	mBluetoothEvent = e;
    }

    public void AutoreadStart()
    {
	mAutoread = true;
    }

    public void AutoreadStop()
    {
	mAutoread = false;
    }

    public boolean verifyDestAddr(BluetoothDevice device)
    {
	String name = device.getName();
	if (name == null)
	    return false;

	if (name.equalsIgnoreCase("SR9_SmartReader")
		|| name.equalsIgnoreCase("TagonReader"))
	{
	    System.out.println(ClassName + " device name = " + name);
	    return true;
	}

	return false;
    }

    public String getPairedAddress()
    {
	String address = null;

	Set<BluetoothDevice> pairedDevice = mAdapter.getBondedDevices();
	if (pairedDevice.size() > 0)
	{
	    for (BluetoothDevice device : pairedDevice)
	    {
		if (D)
		    System.out.println(device.getAddress());
		if (D)
		    System.out.println(device.getName());

		if (verifyDestAddr(device) == true)
		{
		    address = device.getAddress();
		    break;
		}
	    }
	}

	return address;
    }

    public boolean isPairedAddress(String address)
    {
	boolean ret = false;
	Set<BluetoothDevice> pairedDevice = mAdapter.getBondedDevices();
	if (pairedDevice.size() > 0)
	{
	    for (BluetoothDevice device : pairedDevice)
	    {
		if (D)
		    System.out.println(device.getAddress());
		if (D)
		    System.out.println(device.getName());

		if (verifyDestAddr(device) == true)
		{
		    address = device.getAddress();
		    ret = true;
		    break;
		}
	    }
	}

	return ret;
    }

    public String getDiscoveredAddress(Intent i)
    {
	String address = null;

	try
	{
	    BluetoothDevice device = i
		    .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

	    if (D)
		System.out.println(device.getAddress());
	    if (D)
		System.out.println(device.getName());

	    if (verifyDestAddr(device) == true)
	    {
		// stopDiscovery();
		address = device.getAddress();
	    }
	}
	catch (Exception e)
	{
	    e.printStackTrace();
	}

	return address;
    }

    public String getTargetAddress()
    {
	return m_btDestAddr;
    }

    public void setTargetAddress(String address)
    {
	m_btDestAddr = address;
    }

    public void stopDiscovery()
    {
	if (mAdapter.isDiscovering())
	{
	    if (D)
		System.out.println(ClassName + " stopDiscovery");

	    mAdapter.cancelDiscovery();
	}
    }

    public void startDiscovery()
    {
	// If we're already discovering, stop it
	if (mAdapter.isDiscovering())
	{
	    mAdapter.cancelDiscovery();
	}

	m_btDestAddr = null;

	System.out.println(ClassName + " startDiscovery");

	// Request discover from BluetoothAdapter
	mAdapter.startDiscovery();
    }

    public synchronized void disconnect()
    {
	if (mConnectThread != null)
	{
	    mConnectThread.cancel();
	    mConnectThread = null;
	}

	if (mConnectedThread != null)
	{
	    mConnectedThread.cancel();
	    mConnectedThread = null;
	}

	setState(STATE_NONE);
    }

    public synchronized void connect(String address)
    {
	BluetoothDevice device = mAdapter.getRemoteDevice(address);

	if (D)
	    System.out.println("connect to: " + device);

	// Cancel any thread attempting to make a connection
	if (mState == STATE_CONNECTING)
	{
	    if (mConnectThread != null)
	    {
		mConnectThread.cancel();
		mConnectThread = null;
	    }
	}

	// Cancel any thread currently running a connection
	if (mConnectedThread != null)
	{
	    mConnectedThread.cancel();
	    mConnectedThread = null;
	}

	// Start the thread to connect with the given device
	mConnectThread = new ConnectThread(device);
	mConnectThread.start();
	setState(STATE_CONNECTING);
    }

    private class ConnectThread extends Thread
    {
	private final BluetoothSocket mmSocket;
	private final BluetoothDevice mmDevice;
	private String mSocketType;

	public ConnectThread(BluetoothDevice device)
	{
	    mmDevice = device;
	    BluetoothSocket tmp = null;
	    // mSocketType = true;

	    // Get a BluetoothSocket for a connection with the
	    // given BluetoothDevice
	    try
	    {
		// if (secure) {
		tmp = device.createRfcommSocketToServiceRecord(SPP_UUID);
		// }
		// else
		// {
		// tmp = device.createInsecureRfcommSocketToServiceRecord(
		// MY_UUID_INSECURE);
		// }
	    }
	    catch (IOException e)
	    {
		if (D)
		    System.out.println("Socket Type: " + mSocketType
			    + "create() failed" + e);
	    }
	    mmSocket = tmp;
	}

	public void run()
	{
	    if (D)
		System.out.println("BEGIN mConnectThread SocketType:"
			+ mSocketType);
	    setName("ConnectThread" + mSocketType);

	    // Always cancel discovery because it will slow down a connection
	    mAdapter.cancelDiscovery();

	    // Make a connection to the BluetoothSocket
	    try
	    {
		// This is a blocking call and will only return on a
		// successful connection or an exception
		mmSocket.connect();
	    }
	    catch (IOException e)
	    {
		// Close the socket
		try
		{
		    mmSocket.close();
		}
		catch (IOException e2)
		{
		    if (D)
			System.out.println("unable to close() " + mSocketType
				+ " socket during connection failure" + e2);
		}
		connectionFailed();
		return;
	    }

	    // Reset the ConnectThread because we're done
	    synchronized (this)
	    {
		mConnectThread = null;
	    }

	    // Start the connected thread
	    connected(mmSocket, mmDevice, mSocketType);
	}

	public void cancel()
	{
	    try
	    {
		mmSocket.close();
	    }
	    catch (IOException e)
	    {
		if (D)
		    System.out.println("close() of connect " + mSocketType
			    + " socket failed" + e);
	    }
	}
    }

    /**
     * Start the ConnectedThread to begin managing a Bluetooth connection
     * 
     * @param socket
     *            The BluetoothSocket on which the connection was made
     * @param device
     *            The BluetoothDevice that has been connected
     */
    public synchronized void connected(BluetoothSocket socket,
	    BluetoothDevice device, final String socketType)
    {
	if (D)
	    System.out.println("connected, Socket Type:" + socketType);

	// Cancel the thread that completed the connection
	if (mConnectThread != null)
	{
	    mConnectThread.cancel();
	    mConnectThread = null;
	}

	// Cancel any thread currently running a connection
	if (mConnectedThread != null)
	{
	    mConnectedThread.cancel();
	    mConnectedThread = null;
	}

	// Start the thread to manage the connection and perform transmissions
	mConnectedThread = new ConnectedThread(socket, socketType);
	mConnectedThread.start();

	// Send the name of the connected device back to the UI Activity
//	if (mBluetoothEvent != null)
//	    mBluetoothEvent.onConnected();

	setState(STATE_CONNECTED);
    }

    /**
     * Set the current state of the chat connection
     * 
     * @param state
     *            An integer defining the current connection state
     */
    private synchronized void setState(int state)
    {
	if (D)
	    System.out.println("setState() " + mState + " -> " + state);
	mState = state;

	// Give the new state to the Handler so the UI Activity can update
	// mHandler.obtainMessage(BluetoothChat.MESSAGE_STATE_CHANGE, state,
	// -1).sendToTarget();
    }

    /**
     * Indicate that the connection attempt failed and notify the UI Activity.
     */
    private void connectionFailed()
    {
	if (D)
	    System.out.println("Unable to connect device");

	// Send a failure message back to the Activity
	if (mBluetoothEvent != null)
	    mBluetoothEvent.onDisconnected();
    }

    /**
     * Indicate that the connection was lost and notify the UI Activity.
     */
    private void connectionLost()
    {

	if (D)
	    System.out.println("Device connection was lost");

	// Send a failure message back to the Activity
	if (mBluetoothEvent != null)
	    mBluetoothEvent.onDisconnected();
    }

    private void parseScp(byte[] scp)
    {
	boolean converted = false;
	byte[] ba = new byte[scp.length + 2];
	ba[0] = (byte) 0xbb;
	ba[1] = (byte) 0x04;
	// ba[2] = (byte) scp[1]; // command
	ba[3] = (byte) 0x00;
	ba[4] = (byte) scp[2]; // cmd
	for (int i = 5; i < ba.length - 1; i++)
	{
	    ba[i] = scp[i - 2];
	}
	ba[ba.length - 1] = (byte) 0x7e;

	int command = scp[1];

	switch (command)
	{

	case HANDLER_MODE_SR9_CONNECT:
	    ba[2] = (byte) CMD_CONNECT; // command
	    converted = true;
	    break;
	case HANDLER_MODE_SR9_DISCONNECT:
	    ba[2] = (byte) CMD_DISCONNECT; // command
	    converted = true;
	    break;
	case HANDLER_MODE_RFID_TIMEOUT:
	    ba[2] = (byte) CMD_TIMEOUT; // command
	    converted = true;
	    break;
	case HANDLER_MODE_SR9_BATTERY_LOW:
	    ba[2] = (byte) CMD_BATTERY_LOW; // command
	    converted = true;
	    break;
	default:
	    for (int i = 0; i < scp.length; i++)
	    {
		if (D)
		    System.out.printf("%02X ", (int) (scp[i] & 0xff));
	    }
	    break;
	}

	if (converted)
	{
	    notifyBytesAvailable(ba);
	}
    }

    /**
     * This thread runs during a connection with a remote device. It handles all
     * incoming and outgoing transmissions.
     */
    private class ConnectedThread extends Thread
    {
	private final BluetoothSocket mmSocket;
	private final InputStream mmInStream;
	private final OutputStream mmOutStream;

	public ConnectedThread(BluetoothSocket socket, String socketType)
	{
	    if (D)
		System.out.println("create ConnectedThread: " + socketType);
	    mmSocket = socket;
	    InputStream tmpIn = null;
	    OutputStream tmpOut = null;

	    // Get the BluetoothSocket input and output streams
	    try
	    {
		tmpIn = socket.getInputStream();
		tmpOut = socket.getOutputStream();
	    }
	    catch (IOException e)
	    {
		if (D)
		    System.out.println("temp sockets not created" + e);
	    }

	    mmInStream = tmpIn;
	    mmOutStream = tmpOut;
	}

	public void run()
	{
	    if (D)
		System.out.println("BEGIN mConnectedThread");

	    byte[] buffer = new byte[1024];
	    int recvlen;
	    int timer = 0;

	    // Keep listening to the InputStream while connected
	    while (true)
	    {
		try
		{
		    // Read from the InputStream
		    recvlen = mmInStream.read(buffer);

		    for (int i = 0; i < recvlen; i++)
		    {
			System.out.print(Integer
				.toHexString((int) (buffer[i] & 0xff) + 0x100)
				.substring(1).toUpperCase(Locale.US));
		    }

		    if (recvlen > 0)
		    {
			// System.out.printf("===>> RecvData=");
			for (int i = 0; i < recvlen; i++)
			{
			    // System.out.printf("%02X ", rbuf[i]);
			    if (m_incomingRcp.size() == 0
				    && m_incomingScp.size() == 0)
			    {
				switch (buffer[i])
				{
				case RCP_PKT_PRAMBL:
				    comMode = MODE_RCP;
				    timer = 0;
				    break;
				case SCP_HEADER:
				    comMode = MODE_SCP;
				    timer = 0;
				    break;
				default:
				    continue;
				}
			    }

			    if (comMode == MODE_SCP)
			    {
				synchronized (this)
				{
				    m_incomingScp.add(buffer[i]);

				    if (m_incomingScp.size() >= SCP_MIN_LEN)
				    {
					if (m_incomingScp.get(2) > 255)
					{
					    System.out
						    .println(" 2. SCP processInputBuffer: clear");
					    m_incomingRcp.clear();
					    m_incomingScp.clear();
					    comMode = MODE_NONE;
					    break;
					}

					if (m_incomingScp.size() > SCP_MIN_LEN)
					{
					    if (m_incomingScp.size() == ((int) (m_incomingScp
						    .get(2) & 0xff) + SCP_MIN_LEN))
					    {
						if (m_incomingScp
							.get((int) (m_incomingScp
								.get(2) & 0xff)
								+ SCP_MIN_LEN
								- 1) == SCP_TAIL)
						{
						    System.out.println(" ");

						    byte[] ba = new byte[m_incomingScp
							    .size()];

						    int baIndex = 0;

						    for (Byte b : m_incomingScp
							    .toArray(new Byte[] {}))
						    {
							ba[baIndex++] = b
								.byteValue();
						    }

						    parseScp(ba);

						    System.out
							    .println("SCP Received...");

						    m_incomingRcp.clear();
						    m_incomingScp.clear();
						    comMode = MODE_NONE;
						    break;
						}
						else
						{
						    System.out
							    .println(" 3. SCP processInputBuffer: clear");
						    System.out.print(" ");

						    m_incomingRcp.clear();
						    m_incomingScp.clear();
						    comMode = MODE_NONE;
						    break;
						}
					    }
					}

				    }
				}
			    }

			    if (comMode == MODE_RCP)
			    {
				synchronized (this)
				{
				    m_incomingRcp.add(buffer[i]);

				    if (m_incomingRcp.size() >= RCP_HEADEND_CRC_LEN)
				    {
					if (m_incomingRcp.get(4) > 255)
					{
					    System.out
						    .println(" 2. processInputBuffer: clear");
					    m_incomingRcp.clear();
					    m_incomingScp.clear();
					    comMode = MODE_NONE;
					    break;
					}

					if (m_incomingRcp.size() == ((int) (m_incomingRcp
						.get(4) & 0xff) + RCP_HEADEND_LEN))
					{
					    if (m_incomingRcp
						    .get((int) (m_incomingRcp
							    .get(4) & 0xff)
							    + RCP_PRAMBL_LEN
							    + RCP_HEADER_LEN) == RCP_PKT_ENDMRK)
					    {
						System.out.println(" ");

						byte[] ba = new byte[m_incomingRcp
							.size()];

						int baIndex = 0;

						for (Byte b : m_incomingRcp
							.toArray(new Byte[] {}))
						    ba[baIndex++] = b
							    .byteValue();

						System.out
							.println("RCP Received...");

						notifyBytesAvailable(ba);
						m_incomingRcp.clear();
						m_incomingScp.clear();
						comMode = MODE_NONE;
						break;
					    }
					    else
					    {
						System.out
							.println(" 3. processInputBuffer: clear");
						System.out.print(" ");
						m_incomingRcp.clear();
						m_incomingScp.clear();
						comMode = MODE_NONE;
						break;
					    }
					}

				    }

				}
			    }
			}
			// System.out.printf("\n");
			// Handler_SendEvent(rbuf, recvlen);

		    }
		    System.out.println("");

		    // Send the obtained bytes to the UI Activity
		    // mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes,
		    // -1, buffer).sendToTarget();

		    Thread.sleep(m_SleepTime);
		    timer++;
		    if ((timer > (1000 / m_SleepTime))
			    && (comMode != MODE_NONE))
		    {
			m_incomingRcp.clear();
			m_incomingScp.clear();
			comMode = MODE_NONE;
		    }
		}
		catch (InterruptedException e)
		{
		    // TODO Auto-generated catch block
		    e.printStackTrace();
		}
		catch (IOException e)
		{
		    if (D)
			System.out.println("disconnected" + e);

		    m_incomingRcp.clear();
		    m_incomingScp.clear();

		    connectionLost();
		    break;
		}

	    }
	}

	/**
	 * Write to the connected OutStream.
	 * 
	 * @param buffer
	 *            The bytes to write
	 */
	public void write(byte[] buffer)
	{
	    try
	    {
		mmOutStream.write(buffer);

		// Share the sent message back to the UI Activity
		// mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1,
		// buffer).sendToTarget();
	    }
	    catch (IOException e)
	    {
		if (D)
		    System.out.println("Exception during write" + e);
	    }
	}

	public void cancel()
	{
	    try
	    {
		mmSocket.close();
	    }
	    catch (IOException e)
	    {
		if (D)
		    System.out.println("close() of connect socket failed");
	    }
	}
    }

    /**
     * Write to the ConnectedThread in an unsynchronized manner
     * 
     * @param out
     *            The bytes to write
     * @see ConnectedThread#write(byte[])
     */
    public void write(byte[] out)
    {
	// Create temporary object
	ConnectedThread r;
	// Synchronize a copy of the ConnectedThread
	synchronized (this)
	{
	    if (mState != STATE_CONNECTED)
		return;
	    r = mConnectedThread;
	}
	// Perform the write unsynchronized
	r.write(out);
    }

    private OnBytesAvailableListener bytesAvailableListener = null;

    private void notifyBytesAvailable(byte[] rxData)
    {
	for (Byte b : rxData)
	{
	    System.out.print(Integer
		    .toHexString((int) (b.byteValue() & 0xff) + 0x100)
		    .substring(1).toUpperCase(Locale.US));
	}

	int type = rxData[1];
	int command = rxData[2];
	int payload = rxData[5];

	int temp = 0;
	
	if (mAutoread)
	    temp = 1;
	
	System.out.printf("mAutoread = %d, [%02X]", (int) temp, payload);
	
	if (type == 0x02 && command == 0x22 && mAutoread == true)
	{
	    new requestAutoread().execute(0);
	} else
	if (type == 0x01 && command == 0x28 && mAutoread == true)
	{
	    new requestAutoread().execute(0);
	} else	
	if (type == 0x01 && command == (byte) 0xFF && payload == 0x15 && mAutoread == true)
	{
	    System.out.printf("Fail...");
	    new requestAutoread().execute(0);
	    return;
	}
	
	if (bytesAvailableListener != null)
	{
	    bytesAvailableListener.onBytesAvailable(rxData);
	}

    } // End notifyBytesAvailable()

    public void registerBytesAvailableListener(OnBytesAvailableListener listener)
    {
	bytesAvailableListener = listener;
    } //

    public class requestAutoread extends AsyncTask<Integer, Void, Void>
    {
	protected Void doInBackground(Integer... param)
	{
	    System.out.print("requestAutoread: ");
	    
	    try
	    {
		Thread.sleep(100);
	    }
	    catch (InterruptedException e)
	    {
		// TODO Auto-generated catch block
		e.printStackTrace();
	    }

	    //byte[] ba = new byte[] { (byte) 0xBB, 0x00, 0x27, 0x00, 0x03, 0x22,
		    //(byte) 0xFF, (byte) 0xFF, 0x7E };

	    byte[] ba = new byte[] { (byte) 0xBB, 0x00, 0x22, 0x00, 0x00, 0x7E };
	    
	    if (mAutoread)
		write(ba);

	    for (Byte b : ba)
	    {
		System.out.printf("%02X ", b.byteValue() );
	    }
	    System.out.println();

	    return null;
	}
    }
}
